library(tidyverse)
library(knitr)
library(sjPlot)
library(gridExtra)
library(lme4)
library(emmeans)
library(car) # for vif
library(bbmle) # for AICtab
library(broom) # for glance
theme_set(ggthemes::theme_few())

Summary

Mixed modeling with all relevant variables predicting accuracy

From the preregistration, the mixed model was specified thusly:

correct ~ delay * age + 
          task_experience + cup_distance + board_size + trial +
          (1 + delay + trial | site/subject/block/hiding_location ) + 
          (1 + task_experience + cup_distance + board_size + trial + delay | species)

In the dataframe, subject_site = subject, and norm_age should be used for age.

Model as pre-registered has too many random effects

Error: number of observations (=6246) < number of random effects (=10608) for term (1 + delay + trial | hiding_location:(block:(subject_site:site))); the random-effects parameters are probably unidentifiable

Pruning random effects in the following order (from preregistration):

  • Remove correlations between random effects
  • Remove random slopes (in the following order)
    • species
    • hiding_location
    • block
    • subject

Model only converges once we take out hiding_location. After doing so, the other random effects (correlation, site, species) can be put back in.

The model below converges. Model output is saved in 06_mp_model_v2.rds

correct ~ delay * norm_age + 
          task_experience + cup_distance + board_size + trial + 
          (1 + delay + trial | site/subject_site) + 
          (1 + task_experience + cup_distance + board_size + trial + delay | species)

Reduced model

After pruning random effects with little variability and removing board_size, which covaried with cup_distance, the reduced model has the following structure. It is saved in 06_mp_3_model3_v2.rds

correct ~ delay * norm_age + 
          task_experience + cup_distance + board_size + trial + 
          (1 + delay | site/subject_site) + 
          (1 + delay | species)

Data prep

Data import

mp_data <- read.csv("../data/merged_data/01_manyprimates_pilot_merged_data_v2.csv")

Prepare code for pre-registered mixed modeling

  • center cup_distance, board_size and trial
  • filter out spider monkey. Only one data point so far, therefore this is not worth including to explode the number of random effects
model.data <- mp_data %>%
  filter(species != "black_faced_spider_monkey") %>%
  mutate_at(vars(cup_distance, board_size, trial), funs(scale(.)[, 1])) %>%
  mutate(hiding_location = factor(hiding_location),
         delay = fct_relevel(delay, "short"))

Model 1

The model takes a while to run. Run next line to load model output from previous run with structure below.

mm.1 <- readRDS("06_mp_model1.rds")
mm.1 <- glmer(correct ~ delay * norm_age +
               task_experience + cup_distance + board_size + trial +
               (1 + delay + trial | site/subject_site/block) +
               (1 + task_experience + cup_distance + board_size + trial + delay | species)
             , data = model.data
             , family = binomial
             , control = glmerControl(optimizer = "bobyqa", optCtrl = list(maxfun = 2e5))
             )

saveRDS(mm.1, "06_mp_model1.rds")

Some diagnostics

  • examining Cholesky decomposition
theta <- getME(mm.1, "theta")
diag.element <- getME(mm.1, "lower") == 0
any(theta[diag.element] < 1e-5)
[1] TRUE

Model summary

Confirm model structure

# mm.1@call
formula(mm.1)
correct ~ delay * norm_age + task_experience + cup_distance + 
    board_size + trial + (1 + delay + trial | site/subject_site) + 
    (1 + task_experience + cup_distance + board_size + trial + 
        delay | species)
glance(mm.1) %>% kable(digits = 2)
sigma logLik AIC BIC deviance df.residual
1 -3388.28 6892.57 7283.47 6365.11 6188

Random effects

fmt <- function(num, digits) return(round(num, digits))
VarCorr(mm.1) %>% print(formatter = fmt, digits = 3) # comp = c("Variance", "Std.Dev.")
 Groups            Name               Std.Dev. Corr                               
 subject_site:site (Intercept)        0.862                                       
                   delaylong          0.646    -0.91                              
                   delaymedium        0.528    -0.85  0.99                        
                   trial              0.093    -0.31  0.61  0.71                  
 site              (Intercept)        0.917                                       
                   delaylong          0.512    -1.00                              
                   delaymedium        0.627    -1.00  1.00                        
                   trial              0.084     1.00 -1.00 -1.00                  
 species           (Intercept)        0.835                                       
                   task_experienceyes 0.173    -1.00                              
                   cup_distance       0.012     1.00 -1.00                        
                   board_size         0.232    -1.00  1.00 -1.00                  
                   trial              0.021    -1.00  1.00 -1.00  1.00            
                   delaylong          0.494    -1.00  1.00 -1.00  1.00  1.00      
                   delaymedium        0.436    -1.00  1.00 -1.00  1.00  1.00  1.00

Fixed effects

CIs

mm.1.ci <- confint(mm.1, method = "Wald") %>% # bootstrap these later
  as.data.frame %>%
  rownames_to_column %>%
  filter(complete.cases(.)) %>%
  rename(LL = `2.5 %`, UL = `97.5 %`) %>%
  mutate(OR_LL = exp(LL), OR_UL = exp(UL))
coef(summary(mm.1)) %>%
  as.data.frame %>%
  rownames_to_column() %>%
  mutate(OR = exp(Estimate)) %>%
  left_join(mm.1.ci, by = "rowname") %>%
  select(rowname, OR, OR_LL, OR_UL, Estimate, LL, UL, everything()) %>%
  kable(digits = 3)
rowname OR OR_LL OR_UL Estimate LL UL Std. Error z value Pr(>|z|)
(Intercept) 4.135 1.703 10.040 1.420 0.532 2.307 0.453 3.136 0.002
delaylong 0.284 0.169 0.477 -1.257 -1.775 -0.740 0.264 -4.763 0.000
delaymedium 0.369 0.213 0.638 -0.997 -1.545 -0.450 0.279 -3.570 0.000
norm_age 0.987 0.796 1.224 -0.013 -0.228 0.202 0.110 -0.117 0.907
task_experienceyes 1.234 0.784 1.940 0.210 -0.243 0.663 0.231 0.908 0.364
cup_distance 1.554 1.253 1.927 0.441 0.226 0.656 0.110 4.014 0.000
board_size 1.779 1.263 2.506 0.576 0.233 0.919 0.175 3.296 0.001
trial 1.046 0.953 1.148 0.045 -0.048 0.138 0.047 0.948 0.343
delaylong:norm_age 1.049 0.854 1.289 0.048 -0.158 0.253 0.105 0.454 0.650
delaymedium:norm_age 1.082 0.887 1.320 0.079 -0.120 0.278 0.102 0.775 0.438
corr <- cov2cor(vcov(mm.1)) %>% as.matrix %>% round(2)
corr[upper.tri(corr, diag = T)] <- ""
colnames(corr) <- 1:10
rownames(corr) <- str_c(1:10, " ", rownames(corr))

corr %>% as.data.frame %>% select(-10) %>% rownames_to_column

Pairwise contrasts for delay

based on estimated marginal means

Note. This wasn’t in the preregistration.

emmeans(mm.1, pairwise ~ delay, type = "response")$contrasts
 contrast       odds.ratio         SE  df z.ratio p.value
 short / long    3.5160746 0.92828519 Inf   4.762  <.0001
 short / medium  2.7103806 0.75716209 Inf   3.569  0.0010
 long / medium   0.7708541 0.07335892 Inf  -2.735  0.0172

Results are averaged over the levels of: task_experience 
P value adjustment: tukey method for comparing a family of 3 estimates 
Tests are performed on the log odds ratio scale 

Model 1 plots

Fixed effects

plot_model(mm.1, title = "Fixed Effects", order.terms = c(7, 4, 3:1, 9:8, 5, 6),
           width = .3, show.values = T, value.size = 2.5, value.offset = .3) +
  geom_hline(yintercept = 1, lty = 2) +
  ylim(0, 3)

Random effects

ranef.plots <- plot_model(mm.1, type = "re", sort.est = "(Intercept)")

Subject/Site

ranef.plots[[1]]

Site

ranef.plots[[2]]

Species

ranef.plots[[3]]


Pruning the model

  • remove trial random slopes within species as the estimates in the previous models were essentially 0
  • remove trial from the random slopes for subject/site for the same reason
correct ~ delay * norm_age + 
          task_experience + cup_distance + board_size + trial +
          (1 + delay | site/subject_site ) +         
          (1 + task_experience + cup_distance + board_size + delay | species)

Check colinearity in the previous model

col.mm1 <- glm(correct ~ delay + norm_age +
                 task_experience + cup_distance + board_size + trial
               , data = model.data
               , family = binomial)
vif(col.mm1)
                    GVIF Df GVIF^(1/(2*Df))
delay           1.007876  2        1.001963
norm_age        1.072757  1        1.035740
task_experience 1.056383  1        1.027805
cup_distance    1.323990  1        1.150648
board_size      1.296698  1        1.138726
trial           1.001067  1        1.000533

No signs of high colinearity.

Check levels of random effects

Check how many different levels there are within each random effect

source("diagnostic_fcns.r")
overview <- fe.re.tab("correct ~ delay + task_experience + board_size + cup_distance + trial", "species", data = model.data)
overview$summary
$`delay_within_species (factor)`

 3 
11 

$`task_experience_within_species (factor)`

1 2 
9 2 

$`board_size_within_species (covariate)`

1 2 4 
5 5 1 

$`cup_distance_within_species (covariate)`

1 2 4 
6 4 1 

$`trial_within_species (covariate)`

36 
11 

This suggests that, within species, random slopes for task_experience does not make much sense as most species have only 1 level. Same is true for cup_distance and board_size. Indeed, the model summary and random effects plot for species confirm that there is little variability in these estimates (they’re close to zero). Therefore they are removed.

correct ~ delay * norm_age + 
          task_experience + cup_distance + board_size + trial +
          (1 + delay + trial | site/subject_site ) +         
          (1 + delay | species)

Model 2

mm.2 <- readRDS("06_mp_model2.rds")
mm.2 <- glmer(correct ~ delay * norm_age +
              task_experience + cup_distance + board_size  + trial +
              (1 + delay | site/subject_site) +
              (1 + delay | species)
              , data = model.data
              , family = binomial
              , control = glmerControl(optimizer = "bobyqa", optCtrl = list(maxfun = 2e5))
              )

saveRDS(mm.2, "06_mp_model2.rds")

Model summary

Confirm model structure

formula(mm.2)
correct ~ delay * norm_age + task_experience + cup_distance + 
    board_size + trial + (1 + delay | site/subject_site) + (1 + 
    delay | species)
glance(mm.2) %>% kable(digits = 2)
sigma logLik AIC BIC deviance df.residual
1 -3391.26 6838.52 7027.23 6389.41 6218

LRT

drop1(mm.2, test = 'Chisq')
Single term deletions

Model:
correct ~ delay * norm_age + task_experience + cup_distance + 
    board_size + trial + (1 + delay | site/subject_site) + (1 + 
    delay | species)
                Df    AIC     LRT   Pr(Chi)    
<none>             6838.5                      
task_experience  1 6836.5  0.0074 0.9314327    
cup_distance     1 6848.6 12.0934 0.0005060 ***
board_size       1 6847.9 11.3967 0.0007357 ***
trial            1 6837.1  0.5912 0.4419742    
delay:norm_age   2 6835.3  0.8225 0.6628194    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Random effects

VarCorr(mm.2) %>% print(comp = c("Variance", "Std.Dev."), formatter = fmt, digits = 3)
 Groups            Name        Variance Std.Dev. Corr       
 subject_site:site (Intercept) 0.734    0.857               
                   delaylong   0.416    0.645    -0.90      
                   delaymedium 0.261    0.511    -0.85  1.00
 site              (Intercept) 1.087    1.043               
                   delaylong   0.367    0.606    -1.00      
                   delaymedium 0.456    0.675    -1.00  1.00
 species           (Intercept) 0.481    0.694               
                   delaylong   0.214    0.462    -1.00      
                   delaymedium 0.157    0.396    -1.00  1.00

Fixed effects

CIs

mm.2.ci <- readRDS("06_mp_model2_ci.rds")
# Bootstrap function by Roger Mundry @ MPI EVA
source("boot_glmm.r")

mm.2.ci <- boot.glmm.pred(model.res = mm.2, excl.warnings = F, nboots = 1000,
                         para = F, resol = 100, level = 0.95, use = NULL,
                         circ.var.name = NULL, circ.var = NULL, use.u = F,
                         n.cores = c("all-1", "all"), save.path = NULL)

saveRDS(mm.2.ci, "06_mp_model2_ci.rds")
coef(summary(mm.2)) %>%
  as.data.frame %>%
  rownames_to_column() %>%
  mutate(OR = exp(Estimate)) %>%
  cbind(mm.2.ci$ci.estimates[, 2:3]) %>%
  select(rowname, OR, Estimate, X2.5., X97.5., everything()) %>%
  kable(digits = 3)
rowname OR Estimate X2.5. X97.5. Std. Error z value Pr(>|z|)
(Intercept) (Intercept) 5.805 1.759 0.978 2.541 0.448 3.925 0.000
delaylong delaylong 0.240 -1.426 -1.943 -0.939 0.280 -5.092 0.000
delaymedium delaymedium 0.313 -1.161 -1.663 -0.665 0.281 -4.130 0.000
norm_age norm_age 0.992 -0.008 -0.212 0.211 0.109 -0.078 0.938
task_experienceyes task_experienceyes 1.016 0.015 -0.282 0.350 0.173 0.089 0.929
cup_distance cup_distance 1.462 0.380 0.169 0.613 0.103 3.677 0.000
board_size board_size 1.472 0.386 0.198 0.593 0.094 4.097 0.000
trial trial 1.024 0.023 -0.037 0.086 0.030 0.774 0.439
delaylong:norm_age delaylong:norm_age 1.052 0.051 -0.162 0.249 0.106 0.479 0.632
delaymedium:norm_age delaymedium:norm_age 1.093 0.089 -0.117 0.286 0.101 0.883 0.377

Pairwise contrasts for delay

based on estimated marginal means

emmeans(mm.2, pairwise ~ delay)$contrasts
 contrast         estimate        SE  df z.ratio p.value
 short - long    1.4257372 0.2800475 Inf   5.091  <.0001
 short - medium  1.1604769 0.2810297 Inf   4.129  0.0001
 long - medium  -0.2652602 0.0865917 Inf  -3.063  0.0062

Results are averaged over the levels of: task_experience 
Results are given on the log odds ratio (not the response) scale. 
P value adjustment: tukey method for comparing a family of 3 estimates 

code to bootstrap these CIs (confint(emmeans(...) gets Wald CIs)

boot.pairs <- function(m) suppressMessages(summary(pairs(emmeans(m, "delay")))$estimate)
boot.emm <- function(m, n = 1000) {
  boo <- bootMer(m, FUN = boot.pairs, nsim = n, parallel = "multicore", ncpus = 4)
  ci <- sapply(1:3, function(j) quantile(boo$t[,j], prob = c(.025, .975), na.rm = T))
  ci <- t(rbind(boo$t0, ci))
  rownames(ci) <- c("short vs. long", "short vs. medium", "medium vs. long")
  return(ci)
}

This took ~13 hours to run on 4 cores on my machine (jw). Skip this chunk and run the next one to load CIs from previous run.

system.time(mm2.emm.ci <- boot.emm(mm.2, n = 1000))
saveRDS(mm2.emm.ci, "06_mp_model2_emm_ci.rds")
      user     system    elapsed 
130648.652   1127.768  47989.548
mm2.emm.ci <- readRDS("06_mp_model2_emm_ci.rds")
round(mm2.emm.ci, 2)
                        2.5% 97.5%
short vs. long    1.43  0.95  2.01
short vs. medium  1.16  0.67  1.73
medium vs. long  -0.27 -0.44 -0.10

odds ratios

round(exp(mm2.emm.ci), 2)
                      2.5% 97.5%
short vs. long   4.16 2.58  7.46
short vs. medium 3.19 1.94  5.65
medium vs. long  0.77 0.64  0.90

Model 2 plots

Fixed effects

fe.2 <- plot_model(mm.2, title = "A. Fixed Effects", 
                   axis.labels = c("Trial", "Task Experience (yes)","Board Size (cm)", 
                                   "Cup Distance (cm)", "Normed Age x Delay\n(short vs. long)",
                                   "Normed Age x Delay\n(short vs. medium)", "Normed Age", 
                                   "Delay (short vs. long)", "Delay (short vs. medium)"), 
                   order.terms = c(2:1, 3, 9:8, 5, 6, 4, 7), wrap.labels = F,
                   width = .3, show.values = T, value.size = 2.5, value.offset = .3) +
  facet_wrap(~ "") +
  # geom_hline(yintercept = 1, lty = 2) +
  scale_y_continuous(trans = "log", limits = c(.1, 5.5), breaks = c(.1, .2, .5, 1, 2, 5))
fe.2 + theme(plot.margin = unit(c(.5, 7, .5, .5), "cm"))

ggsave("../graphs/05_forestplot.png", fe.2, width = 3, height = 2.5, scale = 2)

Random effects

ranef.plots2 <- plot_model(mm.2, type = "re", sort.est = "(Intercept)")

Subject/Site

ranef.plots2[[1]]

Site

ranef.plots2[[2]]

Species

ranef.plots2[[3]]

Model 3

  • further remove subject/site random effects to look at species differences (from preregistration)
mm.3 <- glmer(correct ~ delay * norm_age +
                task_experience + cup_distance + trial +
                (1 + delay | species)
              , data = model.data
              , family = binomial
              , control = glmerControl(optimizer = "bobyqa", optCtrl = list(maxfun = 2e5))
        )

Random effects

phylo <- read_csv("../data/species_data.csv") %>% select(species, species_formatted, phylo)
plot.data <- get_model_data(mm.3, type = "re", show.values = T) %>%
  left_join(phylo, by = c("term" = "species")) %>%
  rename(species = species_formatted) %>%
  mutate(
    facet = fct_rev(facet),
    facet = fct_recode(facet, "Intercept" = "species (Intercept)",
                       "Delay (short vs. long)" = "delaylong", 
                       "Delay (short vs. medium)" = "delaymedium")
    )
Column `term`/`species` joining factor and character vector, coercing into character vector
sorted <- filter(plot.data, facet == "Intercept") %>% arrange(estimate) %>% with(species)
plot.data <- mutate(plot.data, species = factor(species, levels = sorted))
re.3 <- ggplot(plot.data, aes(x = species, y = estimate, col = group)) +
  facet_grid(~ facet) +
  geom_hline(yintercept = 1, col = "grey90", size = 1.125) +
  # geom_hline(yintercept = 1, lty = 2) +
  geom_errorbar(aes(ymin = conf.low, ymax = conf.high), width = .3) +
  geom_point(size = 2.5) +
  geom_text(aes(label = p.label), nudge_x = .3, size = 2.5, show.legend = F) +
  scale_y_continuous("Odds Ratios", trans = "log", breaks = c(.1, .2, .5, 1, 2, 5,10,20,40)) +
  scale_colour_brewer(palette = "Set1") +
  coord_flip(ylim = c(.1, 42.5)) + xlab("") +
  guides(col = "none") +
  ggtitle("B. Species Random Effects")
mat <- matrix(c(rep(1, 3), rep(2, 7)), nrow = 1)
grid.arrange(fe.2, re.3, layout_matrix = mat)

ggsave("../graphs/05_forestplot_fe_re.png", arrangeGrob(fe.2, re.3, layout_matrix = mat), width = 8, height = 3, scale = 1.8)
ggsave("../graphs/Fig3.tiff", arrangeGrob(fe.2, re.3, layout_matrix = mat), width = 8, height = 3, scale = 1.8, type = "cairo", compression = "lzw")

Model comparison

We’re looking for the lowest AIC(c) as the model with the ‘best fit’ with a reasonable number of parameters. (Too many are penalized by AIC as one way to address overfitting.)

Indeed, the reduced model seems to do a better job of striking that balance between fitting the data with fewer parameters.

AICctab(mm.1, mm.2, mm.3, logLik = T, weights = T)
     dLogLik dAICc df weight
mm.2 109.9     0.0 28 1     
mm.1 112.9    54.9 58 <0.001
mm.3   0.0   193.6 15 <0.001
anova(mm.1, mm.2, mm.3)
Data: model.data
Models:
mm.3: correct ~ delay * norm_age + task_experience + cup_distance + 
mm.3:     trial + (1 + delay | species)
mm.2: correct ~ delay * norm_age + task_experience + cup_distance + 
mm.2:     board_size + trial + (1 + delay | site/subject_site) + (1 + 
mm.2:     delay | species)
mm.1: correct ~ delay * norm_age + task_experience + cup_distance + 
mm.1:     board_size + trial + (1 + delay + trial | site/subject_site) + 
mm.1:     (1 + task_experience + cup_distance + board_size + trial + 
mm.1:         delay | species)
     Df    AIC    BIC  logLik deviance    Chisq Chi Df Pr(>Chisq)    
mm.3 15 7032.3 7133.4 -3501.1   7002.3                               
mm.2 28 6838.5 7027.2 -3391.3   6782.5 219.7490     13     <2e-16 ***
mm.1 58 6892.6 7283.5 -3388.3   6776.6   5.9521     30          1    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Difference in regression coefficients

Difference

coef1 <- coef(summary(mm.1))[c(2, 3, 6), 1]
coef2 <- coef(summary(mm.2))[c(2, 3, 6), 1]
coef2 - coef1
   delaylong  delaymedium cup_distance 
 -0.16840056  -0.16341754  -0.06131301 

Difference in odds ratios

exp(coef2) - exp(coef1)
   delaylong  delaymedium cup_distance 
 -0.04407273  -0.05561161  -0.09242183 
LS0tCnRpdGxlOiAiTWFueVByaW1hdGVzIHBpbG90IG1peGVkIG1vZGVsaW5nIgphdXRob3I6ICJEcmV3IEFsdHNjaHVsLCBNYW51ZWwgQm9obiwgSnVsaWEgV2F0emVrIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGNzczogc3R5bGUuY3NzCiAgICB0aGVtZTogcGFwZXIKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwotLS0KCmBgYHtyIHNldHVwLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShrbml0cikKbGlicmFyeShzalBsb3QpCmxpYnJhcnkoZ3JpZEV4dHJhKQpsaWJyYXJ5KGxtZTQpCmxpYnJhcnkoZW1tZWFucykKbGlicmFyeShjYXIpICMgZm9yIHZpZgpsaWJyYXJ5KGJibWxlKSAjIGZvciBBSUN0YWIKbGlicmFyeShicm9vbSkgIyBmb3IgZ2xhbmNlCgp0aGVtZV9zZXQoZ2d0aGVtZXM6OnRoZW1lX2ZldygpKQpgYGAKCiMgU3VtbWFyeQoKTWl4ZWQgbW9kZWxpbmcgd2l0aCBhbGwgcmVsZXZhbnQgdmFyaWFibGVzIHByZWRpY3RpbmcgYWNjdXJhY3kKCkZyb20gdGhlIHByZXJlZ2lzdHJhdGlvbiwgdGhlIG1peGVkIG1vZGVsIHdhcyBzcGVjaWZpZWQgdGh1c2x5OgoKYGBgCmNvcnJlY3QgfiBkZWxheSAqIGFnZSArIAogICAgICAgICAgdGFza19leHBlcmllbmNlICsgY3VwX2Rpc3RhbmNlICsgYm9hcmRfc2l6ZSArIHRyaWFsICsKICAgICAgICAgICgxICsgZGVsYXkgKyB0cmlhbCB8IHNpdGUvc3ViamVjdC9ibG9jay9oaWRpbmdfbG9jYXRpb24gKSArIAogICAgICAgICAgKDEgKyB0YXNrX2V4cGVyaWVuY2UgKyBjdXBfZGlzdGFuY2UgKyBib2FyZF9zaXplICsgdHJpYWwgKyBkZWxheSB8IHNwZWNpZXMpCmBgYAoKSW4gdGhlIGRhdGFmcmFtZSwgCmBzdWJqZWN0X3NpdGUgPSBzdWJqZWN0YCwKYW5kIGBub3JtX2FnZWAgc2hvdWxkIGJlIHVzZWQgZm9yIGBhZ2VgLgoKTW9kZWwgYXMgcHJlLXJlZ2lzdGVyZWQgaGFzIHRvbyBtYW55IHJhbmRvbSBlZmZlY3RzCgpgYGAKRXJyb3I6IG51bWJlciBvZiBvYnNlcnZhdGlvbnMgKD02MjQ2KSA8IG51bWJlciBvZiByYW5kb20gZWZmZWN0cyAoPTEwNjA4KSBmb3IgdGVybSAoMSArIGRlbGF5ICsgdHJpYWwgfCBoaWRpbmdfbG9jYXRpb246KGJsb2NrOihzdWJqZWN0X3NpdGU6c2l0ZSkpKTsgdGhlIHJhbmRvbS1lZmZlY3RzIHBhcmFtZXRlcnMgYXJlIHByb2JhYmx5IHVuaWRlbnRpZmlhYmxlCmBgYAoKUHJ1bmluZyByYW5kb20gZWZmZWN0cyBpbiB0aGUgZm9sbG93aW5nIG9yZGVyIChmcm9tIHByZXJlZ2lzdHJhdGlvbik6IAoKPiAtIFJlbW92ZSBjb3JyZWxhdGlvbnMgYmV0d2VlbiByYW5kb20gZWZmZWN0cwo+IC0gUmVtb3ZlIHJhbmRvbSBzbG9wZXMgKGluIHRoZSBmb2xsb3dpbmcgb3JkZXIpCj4gICAgIC0gYHNwZWNpZXNgCj4gICAgIC0gYGhpZGluZ19sb2NhdGlvbmAKPiAgICAgLSBgYmxvY2tgCj4gICAgIC0gYHN1YmplY3RgCgpNb2RlbCBvbmx5IGNvbnZlcmdlcyBvbmNlIHdlIHRha2Ugb3V0IGBoaWRpbmdfbG9jYXRpb25gLiBBZnRlciBkb2luZyBzbywgdGhlIG90aGVyIHJhbmRvbSBlZmZlY3RzIChjb3JyZWxhdGlvbiwgc2l0ZSwgc3BlY2llcykgY2FuIGJlIHB1dCBiYWNrIGluLgoKVGhlIG1vZGVsIGJlbG93IGNvbnZlcmdlcy4gTW9kZWwgb3V0cHV0IGlzIHNhdmVkIGluIGAwNl9tcF9tb2RlbF92Mi5yZHNgCgpgYGAKY29ycmVjdCB+IGRlbGF5ICogbm9ybV9hZ2UgKyAKICAgICAgICAgIHRhc2tfZXhwZXJpZW5jZSArIGN1cF9kaXN0YW5jZSArIGJvYXJkX3NpemUgKyB0cmlhbCArIAogICAgICAgICAgKDEgKyBkZWxheSArIHRyaWFsIHwgc2l0ZS9zdWJqZWN0X3NpdGUpICsgCiAgICAgICAgICAoMSArIHRhc2tfZXhwZXJpZW5jZSArIGN1cF9kaXN0YW5jZSArIGJvYXJkX3NpemUgKyB0cmlhbCArIGRlbGF5IHwgc3BlY2llcykKYGBgCgojIyBSZWR1Y2VkIG1vZGVsCgpBZnRlciBwcnVuaW5nIHJhbmRvbSBlZmZlY3RzIHdpdGggbGl0dGxlIHZhcmlhYmlsaXR5IGFuZCByZW1vdmluZyBgYm9hcmRfc2l6ZWAsIHdoaWNoIGNvdmFyaWVkIHdpdGggYGN1cF9kaXN0YW5jZWAsIHRoZSByZWR1Y2VkIG1vZGVsIGhhcyB0aGUgZm9sbG93aW5nIHN0cnVjdHVyZS4gSXQgaXMgc2F2ZWQgaW4gYDA2X21wXzNfbW9kZWwzX3YyLnJkc2AKCmBgYApjb3JyZWN0IH4gZGVsYXkgKiBub3JtX2FnZSArIAogICAgICAgICAgdGFza19leHBlcmllbmNlICsgY3VwX2Rpc3RhbmNlICsgYm9hcmRfc2l6ZSArIHRyaWFsICsgCiAgICAgICAgICAoMSArIGRlbGF5IHwgc2l0ZS9zdWJqZWN0X3NpdGUpICsgCiAgICAgICAgICAoMSArIGRlbGF5IHwgc3BlY2llcykKYGBgCgohW10oLi4vZ3JhcGhzLzA1X2ZvcmVzdHBsb3QucG5nKQoKKioqCgojIERhdGEgcHJlcAoKRGF0YSBpbXBvcnQKCmBgYHtyIGxvYWRpbmcgZGF0YX0KbXBfZGF0YSA8LSByZWFkLmNzdigiLi4vZGF0YS9tZXJnZWRfZGF0YS8wMV9tYW55cHJpbWF0ZXNfcGlsb3RfbWVyZ2VkX2RhdGFfdjIuY3N2IikKYGBgCgpQcmVwYXJlIGNvZGUgZm9yIHByZS1yZWdpc3RlcmVkIG1peGVkIG1vZGVsaW5nCgotIGNlbnRlciBgY3VwX2Rpc3RhbmNlYCwgYGJvYXJkX3NpemVgIGFuZCBgdHJpYWxgCi0gZmlsdGVyIG91dCBzcGlkZXIgbW9ua2V5LiBPbmx5IG9uZSBkYXRhIHBvaW50IHNvIGZhciwgdGhlcmVmb3JlIHRoaXMgaXMgbm90IHdvcnRoIGluY2x1ZGluZyB0byBleHBsb2RlIHRoZSBudW1iZXIgb2YgcmFuZG9tIGVmZmVjdHMKCmBgYHtyfQptb2RlbC5kYXRhIDwtIG1wX2RhdGEgJT4lCiAgZmlsdGVyKHNwZWNpZXMgIT0gImJsYWNrX2ZhY2VkX3NwaWRlcl9tb25rZXkiKSAlPiUKICBtdXRhdGVfYXQodmFycyhjdXBfZGlzdGFuY2UsIGJvYXJkX3NpemUsIHRyaWFsKSwgZnVucyhzY2FsZSguKVssIDFdKSkgJT4lCiAgbXV0YXRlKGhpZGluZ19sb2NhdGlvbiA9IGZhY3RvcihoaWRpbmdfbG9jYXRpb24pLAogICAgICAgICBkZWxheSA9IGZjdF9yZWxldmVsKGRlbGF5LCAic2hvcnQiKSkKYGBgCgojIE1vZGVsIDEKClRoZSBtb2RlbCB0YWtlcyBhIHdoaWxlIHRvIHJ1bi4gUnVuIG5leHQgbGluZSB0byBsb2FkIG1vZGVsIG91dHB1dCBmcm9tIHByZXZpb3VzIHJ1biB3aXRoIHN0cnVjdHVyZSBiZWxvdy4KCmBgYHtyfQptbS4xIDwtIHJlYWRSRFMoIjA2X21wX21vZGVsMS5yZHMiKQpgYGAKCmBgYHtyLCBldmFsPUZBTFNFfQptbS4xIDwtIGdsbWVyKGNvcnJlY3QgfiBkZWxheSAqIG5vcm1fYWdlICsKICAgICAgICAgICAgICAgdGFza19leHBlcmllbmNlICsgY3VwX2Rpc3RhbmNlICsgYm9hcmRfc2l6ZSArIHRyaWFsICsKICAgICAgICAgICAgICAgKDEgKyBkZWxheSArIHRyaWFsIHwgc2l0ZS9zdWJqZWN0X3NpdGUvYmxvY2spICsKICAgICAgICAgICAgICAgKDEgKyB0YXNrX2V4cGVyaWVuY2UgKyBjdXBfZGlzdGFuY2UgKyBib2FyZF9zaXplICsgdHJpYWwgKyBkZWxheSB8IHNwZWNpZXMpCiAgICAgICAgICAgICAsIGRhdGEgPSBtb2RlbC5kYXRhCiAgICAgICAgICAgICAsIGZhbWlseSA9IGJpbm9taWFsCiAgICAgICAgICAgICAsIGNvbnRyb2wgPSBnbG1lckNvbnRyb2wob3B0aW1pemVyID0gImJvYnlxYSIsIG9wdEN0cmwgPSBsaXN0KG1heGZ1biA9IDJlNSkpCiAgICAgICAgICAgICApCgpzYXZlUkRTKG1tLjEsICIwNl9tcF9tb2RlbDEucmRzIikKYGBgCgpTb21lIGRpYWdub3N0aWNzCgotIGV4YW1pbmluZyBDaG9sZXNreSBkZWNvbXBvc2l0aW9uCgpgYGB7cn0KdGhldGEgPC0gZ2V0TUUobW0uMSwgInRoZXRhIikKZGlhZy5lbGVtZW50IDwtIGdldE1FKG1tLjEsICJsb3dlciIpID09IDAKYW55KHRoZXRhW2RpYWcuZWxlbWVudF0gPCAxZS01KQpgYGAKCiMjIE1vZGVsIHN1bW1hcnkKCkNvbmZpcm0gbW9kZWwgc3RydWN0dXJlCgpgYGB7cn0KIyBtbS4xQGNhbGwKZm9ybXVsYShtbS4xKQpgYGAKCmBgYHtyLCByZXN1bHRzPSJhc2lzIn0KZ2xhbmNlKG1tLjEpICU+JSBrYWJsZShkaWdpdHMgPSAyKQpgYGAKCiMjIFJhbmRvbSBlZmZlY3RzCgpgYGB7cn0KZm10IDwtIGZ1bmN0aW9uKG51bSwgZGlnaXRzKSByZXR1cm4ocm91bmQobnVtLCBkaWdpdHMpKQpWYXJDb3JyKG1tLjEpICU+JSBwcmludChmb3JtYXR0ZXIgPSBmbXQsIGRpZ2l0cyA9IDMpICMgY29tcCA9IGMoIlZhcmlhbmNlIiwgIlN0ZC5EZXYuIikKYGBgCgojIyBGaXhlZCBlZmZlY3RzCgpDSXMKCmBgYHtyfQptbS4xLmNpIDwtIGNvbmZpbnQobW0uMSwgbWV0aG9kID0gIldhbGQiKSAlPiUgIyBib290c3RyYXAgdGhlc2UgbGF0ZXIKICBhcy5kYXRhLmZyYW1lICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbiAlPiUKICBmaWx0ZXIoY29tcGxldGUuY2FzZXMoLikpICU+JQogIHJlbmFtZShMTCA9IGAyLjUgJWAsIFVMID0gYDk3LjUgJWApICU+JQogIG11dGF0ZShPUl9MTCA9IGV4cChMTCksIE9SX1VMID0gZXhwKFVMKSkKYGBgCgpgYGB7ciwgcmVzdWx0cz0iYXNpcyJ9CmNvZWYoc3VtbWFyeShtbS4xKSkgJT4lCiAgYXMuZGF0YS5mcmFtZSAlPiUKICByb3duYW1lc190b19jb2x1bW4oKSAlPiUKICBtdXRhdGUoT1IgPSBleHAoRXN0aW1hdGUpKSAlPiUKICBsZWZ0X2pvaW4obW0uMS5jaSwgYnkgPSAicm93bmFtZSIpICU+JQogIHNlbGVjdChyb3duYW1lLCBPUiwgT1JfTEwsIE9SX1VMLCBFc3RpbWF0ZSwgTEwsIFVMLCBldmVyeXRoaW5nKCkpICU+JQogIGthYmxlKGRpZ2l0cyA9IDMpCmBgYAoKPCEtLSAjIyBDb3JyZWxhdGlvbiBvZiBGaXhlZCBFZmZlY3RzIC0tPgoKYGBge3IsIGV2YWw9RkFMU0UsIHJlc3VsdHM9ImFzaXMifQpjb3JyIDwtIGNvdjJjb3IodmNvdihtbS4xKSkgJT4lIGFzLm1hdHJpeCAlPiUgcm91bmQoMikKY29yclt1cHBlci50cmkoY29yciwgZGlhZyA9IFQpXSA8LSAiIgpjb2xuYW1lcyhjb3JyKSA8LSAxOjEwCnJvd25hbWVzKGNvcnIpIDwtIHN0cl9jKDE6MTAsICIgIiwgcm93bmFtZXMoY29ycikpCgpjb3JyICU+JSBhcy5kYXRhLmZyYW1lICU+JSBzZWxlY3QoLTEwKSAlPiUgcm93bmFtZXNfdG9fY29sdW1uCmBgYAoKIyMgUGFpcndpc2UgY29udHJhc3RzIGZvciBkZWxheQoKYmFzZWQgb24gZXN0aW1hdGVkIG1hcmdpbmFsIG1lYW5zCgoqTm90ZS4gVGhpcyB3YXNuJ3QgaW4gdGhlIHByZXJlZ2lzdHJhdGlvbi4qCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KZW1tZWFucyhtbS4xLCBwYWlyd2lzZSB+IGRlbGF5LCB0eXBlID0gInJlc3BvbnNlIikkY29udHJhc3RzCmBgYAoKIyBNb2RlbCAxIHBsb3RzCgojIyBGaXhlZCBlZmZlY3RzCgpgYGB7ciwgZmlnLndpZHRoPTQsIGZpZy5oZWlnaHQ9Mi41LCBtZXNzYWdlPUZBTFNFfQpwbG90X21vZGVsKG1tLjEsIHRpdGxlID0gIkZpeGVkIEVmZmVjdHMiLCBvcmRlci50ZXJtcyA9IGMoNywgNCwgMzoxLCA5OjgsIDUsIDYpLAogICAgICAgICAgIHdpZHRoID0gLjMsIHNob3cudmFsdWVzID0gVCwgdmFsdWUuc2l6ZSA9IDIuNSwgdmFsdWUub2Zmc2V0ID0gLjMpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxLCBsdHkgPSAyKSArCiAgeWxpbSgwLCAzKQpgYGAKCiMjIFJhbmRvbSBlZmZlY3RzCgpgYGB7cn0KcmFuZWYucGxvdHMgPC0gcGxvdF9tb2RlbChtbS4xLCB0eXBlID0gInJlIiwgc29ydC5lc3QgPSAiKEludGVyY2VwdCkiKQpgYGAKCiMjIyBTdWJqZWN0L1NpdGUKCmBgYHtyIGZpZy5oZWlnaHQ9MjAsIGZpZy53aWR0aD0xMH0KcmFuZWYucGxvdHNbWzFdXQpgYGAKCiMjIyBTaXRlCgpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTh9CnJhbmVmLnBsb3RzW1syXV0KYGBgCgojIyMgU3BlY2llcwoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0zfQpyYW5lZi5wbG90c1tbM11dCmBgYAoKKioqCgojIFBydW5pbmcgdGhlIG1vZGVsCgotIHJlbW92ZSBgdHJpYWxgIHJhbmRvbSBzbG9wZXMgd2l0aGluIGBzcGVjaWVzYCBhcyB0aGUgZXN0aW1hdGVzIGluIHRoZSBwcmV2aW91cyBtb2RlbHMgd2VyZSBlc3NlbnRpYWxseSAwCi0gcmVtb3ZlIGB0cmlhbGAgZnJvbSB0aGUgcmFuZG9tIHNsb3BlcyBmb3IgYHN1YmplY3Qvc2l0ZWAgZm9yIHRoZSBzYW1lIHJlYXNvbgoKYGBgCmNvcnJlY3QgfiBkZWxheSAqIG5vcm1fYWdlICsgCiAgICAgICAgICB0YXNrX2V4cGVyaWVuY2UgKyBjdXBfZGlzdGFuY2UgKyBib2FyZF9zaXplICsgdHJpYWwgKwogICAgICAgICAgKDEgKyBkZWxheSB8IHNpdGUvc3ViamVjdF9zaXRlICkgKyAgICAgICAgIAogICAgICAgICAgKDEgKyB0YXNrX2V4cGVyaWVuY2UgKyBjdXBfZGlzdGFuY2UgKyBib2FyZF9zaXplICsgZGVsYXkgfCBzcGVjaWVzKQpgYGAKCiMjIENoZWNrIGNvbGluZWFyaXR5IGluIHRoZSBwcmV2aW91cyBtb2RlbAoKYGBge3J9CmNvbC5tbTEgPC0gZ2xtKGNvcnJlY3QgfiBkZWxheSArIG5vcm1fYWdlICsKICAgICAgICAgICAgICAgICB0YXNrX2V4cGVyaWVuY2UgKyBjdXBfZGlzdGFuY2UgKyBib2FyZF9zaXplICsgdHJpYWwKICAgICAgICAgICAgICAgLCBkYXRhID0gbW9kZWwuZGF0YQogICAgICAgICAgICAgICAsIGZhbWlseSA9IGJpbm9taWFsKQoKdmlmKGNvbC5tbTEpCmBgYAoKTm8gc2lnbnMgb2YgaGlnaCBjb2xpbmVhcml0eS4KCiMjIENoZWNrIGxldmVscyBvZiByYW5kb20gZWZmZWN0cwoKQ2hlY2sgaG93IG1hbnkgZGlmZmVyZW50IGxldmVscyB0aGVyZSBhcmUgd2l0aGluIGVhY2ggcmFuZG9tIGVmZmVjdAoKYGBge3J9CnNvdXJjZSgiZGlhZ25vc3RpY19mY25zLnIiKQoKb3ZlcnZpZXcgPC0gZmUucmUudGFiKCJjb3JyZWN0IH4gZGVsYXkgKyB0YXNrX2V4cGVyaWVuY2UgKyBib2FyZF9zaXplICsgY3VwX2Rpc3RhbmNlICsgdHJpYWwiLCAic3BlY2llcyIsIGRhdGEgPSBtb2RlbC5kYXRhKQoKb3ZlcnZpZXckc3VtbWFyeQpgYGAKClRoaXMgc3VnZ2VzdHMgdGhhdCwgd2l0aGluIHNwZWNpZXMsIHJhbmRvbSBzbG9wZXMgZm9yIGB0YXNrX2V4cGVyaWVuY2VgIGRvZXMgbm90IG1ha2UgbXVjaCBzZW5zZSBhcyBtb3N0IHNwZWNpZXMgaGF2ZSBvbmx5IDEgbGV2ZWwuIFNhbWUgaXMgdHJ1ZSBmb3IgYGN1cF9kaXN0YW5jZWAgYW5kIGBib2FyZF9zaXplYC4gSW5kZWVkLCB0aGUgbW9kZWwgc3VtbWFyeSBhbmQgcmFuZG9tIGVmZmVjdHMgcGxvdCBmb3IgYHNwZWNpZXNgIGNvbmZpcm0gdGhhdCB0aGVyZSBpcyBsaXR0bGUgdmFyaWFiaWxpdHkgaW4gdGhlc2UgZXN0aW1hdGVzICh0aGV5J3JlIGNsb3NlIHRvIHplcm8pLiBUaGVyZWZvcmUgdGhleSBhcmUgcmVtb3ZlZC4KCmBgYApjb3JyZWN0IH4gZGVsYXkgKiBub3JtX2FnZSArIAogICAgICAgICAgdGFza19leHBlcmllbmNlICsgY3VwX2Rpc3RhbmNlICsgYm9hcmRfc2l6ZSArIHRyaWFsICsKICAgICAgICAgICgxICsgZGVsYXkgKyB0cmlhbCB8IHNpdGUvc3ViamVjdF9zaXRlICkgKyAgICAgICAgIAogICAgICAgICAgKDEgKyBkZWxheSB8IHNwZWNpZXMpCmBgYAoKIyBNb2RlbCAyCgpgYGB7cn0KbW0uMiA8LSByZWFkUkRTKCIwNl9tcF9tb2RlbDIucmRzIikKYGBgCgpgYGB7ciwgZXZhbD1GQUxTRX0KbW0uMiA8LSBnbG1lcihjb3JyZWN0IH4gZGVsYXkgKiBub3JtX2FnZSArCiAgICAgICAgICAgICAgdGFza19leHBlcmllbmNlICsgY3VwX2Rpc3RhbmNlICsgYm9hcmRfc2l6ZSAgKyB0cmlhbCArCiAgICAgICAgICAgICAgKDEgKyBkZWxheSB8IHNpdGUvc3ViamVjdF9zaXRlKSArCiAgICAgICAgICAgICAgKDEgKyBkZWxheSB8IHNwZWNpZXMpCiAgICAgICAgICAgICAgLCBkYXRhID0gbW9kZWwuZGF0YQogICAgICAgICAgICAgICwgZmFtaWx5ID0gYmlub21pYWwKICAgICAgICAgICAgICAsIGNvbnRyb2wgPSBnbG1lckNvbnRyb2wob3B0aW1pemVyID0gImJvYnlxYSIsIG9wdEN0cmwgPSBsaXN0KG1heGZ1biA9IDJlNSkpCiAgICAgICAgICAgICAgKQoKc2F2ZVJEUyhtbS4yLCAiMDZfbXBfbW9kZWwyLnJkcyIpCmBgYAoKIyMgTW9kZWwgc3VtbWFyeQoKQ29uZmlybSBtb2RlbCBzdHJ1Y3R1cmUKCmBgYHtyfQpmb3JtdWxhKG1tLjIpCmBgYAoKYGBge3IsIHJlc3VsdHM9ImFzaXMifQpnbGFuY2UobW0uMikgJT4lIGthYmxlKGRpZ2l0cyA9IDIpCmBgYAoKIyMgTFJUCgpgYGB7ciwgZXZhbD1GQUxTRX0KZHJvcDEobW0uMiwgdGVzdCA9ICdDaGlzcScpCmBgYAoKYGBgClNpbmdsZSB0ZXJtIGRlbGV0aW9ucwoKTW9kZWw6CmNvcnJlY3QgfiBkZWxheSAqIG5vcm1fYWdlICsgdGFza19leHBlcmllbmNlICsgY3VwX2Rpc3RhbmNlICsgCiAgICBib2FyZF9zaXplICsgdHJpYWwgKyAoMSArIGRlbGF5IHwgc2l0ZS9zdWJqZWN0X3NpdGUpICsgKDEgKyAKICAgIGRlbGF5IHwgc3BlY2llcykKICAgICAgICAgICAgICAgIERmICAgIEFJQyAgICAgTFJUICAgUHIoQ2hpKSAgICAKPG5vbmU+ICAgICAgICAgICAgIDY4MzguNSAgICAgICAgICAgICAgICAgICAgICAKdGFza19leHBlcmllbmNlICAxIDY4MzYuNSAgMC4wMDc0IDAuOTMxNDMyNyAgICAKY3VwX2Rpc3RhbmNlICAgICAxIDY4NDguNiAxMi4wOTM0IDAuMDAwNTA2MCAqKioKYm9hcmRfc2l6ZSAgICAgICAxIDY4NDcuOSAxMS4zOTY3IDAuMDAwNzM1NyAqKioKdHJpYWwgICAgICAgICAgICAxIDY4MzcuMSAgMC41OTEyIDAuNDQxOTc0MiAgICAKZGVsYXk6bm9ybV9hZ2UgICAyIDY4MzUuMyAgMC44MjI1IDAuNjYyODE5NCAgICAKLS0tClNpZ25pZi4gY29kZXM6ICAwIOKAmCoqKuKAmSAwLjAwMSDigJgqKuKAmSAwLjAxIOKAmCrigJkgMC4wNSDigJgu4oCZIDAuMSDigJgg4oCZIDEKYGBgCgoKIyMgUmFuZG9tIGVmZmVjdHMKCmBgYHtyfQpWYXJDb3JyKG1tLjIpICU+JSBwcmludChjb21wID0gYygiVmFyaWFuY2UiLCAiU3RkLkRldi4iKSwgZm9ybWF0dGVyID0gZm10LCBkaWdpdHMgPSAzKQpgYGAKCiMjIEZpeGVkIGVmZmVjdHMKCkNJcwoKYGBge3J9Cm1tLjIuY2kgPC0gcmVhZFJEUygiMDZfbXBfbW9kZWwyX2NpLnJkcyIpCmBgYAoKYGBge3IsIGV2YWw9RkFMU0V9CiMgQm9vdHN0cmFwIGZ1bmN0aW9uIGJ5IFJvZ2VyIE11bmRyeSBAIE1QSSBFVkEKc291cmNlKCJib290X2dsbW0uciIpCgptbS4yLmNpIDwtIGJvb3QuZ2xtbS5wcmVkKG1vZGVsLnJlcyA9IG1tLjIsIGV4Y2wud2FybmluZ3MgPSBGLCBuYm9vdHMgPSAxMDAwLAogICAgICAgICAgICAgICAgICAgICAgICAgcGFyYSA9IEYsIHJlc29sID0gMTAwLCBsZXZlbCA9IDAuOTUsIHVzZSA9IE5VTEwsCiAgICAgICAgICAgICAgICAgICAgICAgICBjaXJjLnZhci5uYW1lID0gTlVMTCwgY2lyYy52YXIgPSBOVUxMLCB1c2UudSA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICBuLmNvcmVzID0gYygiYWxsLTEiLCAiYWxsIiksIHNhdmUucGF0aCA9IE5VTEwpCgpzYXZlUkRTKG1tLjIuY2ksICIwNl9tcF9tb2RlbDJfY2kucmRzIikKYGBgCgpgYGB7ciwgcmVzdWx0cz0iYXNpcyJ9CmNvZWYoc3VtbWFyeShtbS4yKSkgJT4lCiAgYXMuZGF0YS5mcmFtZSAlPiUKICByb3duYW1lc190b19jb2x1bW4oKSAlPiUKICBtdXRhdGUoT1IgPSBleHAoRXN0aW1hdGUpKSAlPiUKICBjYmluZChtbS4yLmNpJGNpLmVzdGltYXRlc1ssIDI6M10pICU+JQogIHNlbGVjdChyb3duYW1lLCBPUiwgRXN0aW1hdGUsIFgyLjUuLCBYOTcuNS4sIGV2ZXJ5dGhpbmcoKSkgJT4lCiAga2FibGUoZGlnaXRzID0gMykKYGBgCgojIyBQYWlyd2lzZSBjb250cmFzdHMgZm9yIGRlbGF5CgpiYXNlZCBvbiBlc3RpbWF0ZWQgbWFyZ2luYWwgbWVhbnMKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQplbW1lYW5zKG1tLjIsIHBhaXJ3aXNlIH4gZGVsYXkpJGNvbnRyYXN0cwpgYGAKCmNvZGUgdG8gYm9vdHN0cmFwIHRoZXNlIENJcyAoYGNvbmZpbnQoZW1tZWFucyguLi4pYCBnZXRzIFdhbGQgQ0lzKQoKYGBge3J9CmJvb3QucGFpcnMgPC0gZnVuY3Rpb24obSkgc3VwcHJlc3NNZXNzYWdlcyhzdW1tYXJ5KHBhaXJzKGVtbWVhbnMobSwgImRlbGF5IikpKSRlc3RpbWF0ZSkKCmJvb3QuZW1tIDwtIGZ1bmN0aW9uKG0sIG4gPSAxMDAwKSB7CiAgYm9vIDwtIGJvb3RNZXIobSwgRlVOID0gYm9vdC5wYWlycywgbnNpbSA9IG4sIHBhcmFsbGVsID0gIm11bHRpY29yZSIsIG5jcHVzID0gNCkKICBjaSA8LSBzYXBwbHkoMTozLCBmdW5jdGlvbihqKSBxdWFudGlsZShib28kdFssal0sIHByb2IgPSBjKC4wMjUsIC45NzUpLCBuYS5ybSA9IFQpKQogIGNpIDwtIHQocmJpbmQoYm9vJHQwLCBjaSkpCiAgcm93bmFtZXMoY2kpIDwtIGMoInNob3J0IHZzLiBsb25nIiwgInNob3J0IHZzLiBtZWRpdW0iLCAibWVkaXVtIHZzLiBsb25nIikKCiAgcmV0dXJuKGNpKQp9CmBgYAoKVGhpcyB0b29rIH4xMyBob3VycyB0byBydW4gb24gNCBjb3JlcyBvbiBteSBtYWNoaW5lIChqdykuIFNraXAgdGhpcyBjaHVuayBhbmQgcnVuIHRoZSBuZXh0IG9uZSB0byBsb2FkIENJcyBmcm9tIHByZXZpb3VzIHJ1bi4KCmBgYHtyLCBldmFsPUZBTFNFfQpzeXN0ZW0udGltZShtbTIuZW1tLmNpIDwtIGJvb3QuZW1tKG1tLjIsIG4gPSAxMDAwKSkKc2F2ZVJEUyhtbTIuZW1tLmNpLCAiMDZfbXBfbW9kZWwyX2VtbV9jaS5yZHMiKQpgYGAKCmBgYAogICAgICB1c2VyICAgICBzeXN0ZW0gICAgZWxhcHNlZCAKMTMwNjQ4LjY1MiAgIDExMjcuNzY4ICA0Nzk4OS41NDgKYGBgCgpgYGB7cn0KbW0yLmVtbS5jaSA8LSByZWFkUkRTKCIwNl9tcF9tb2RlbDJfZW1tX2NpLnJkcyIpCnJvdW5kKG1tMi5lbW0uY2ksIDIpCmBgYAoKb2RkcyByYXRpb3MKCmBgYHtyfQpyb3VuZChleHAobW0yLmVtbS5jaSksIDIpCmBgYAoKIyBNb2RlbCAyIHBsb3RzCgojIyBGaXhlZCBlZmZlY3RzCgpgYGB7ciwgZmlnLndpZHRoPTQsIGZpZy5oZWlnaHQ9Mi41LCBtZXNzYWdlPUZBTFNFfQpmZS4yIDwtIHBsb3RfbW9kZWwobW0uMiwgdGl0bGUgPSAiQS4gRml4ZWQgRWZmZWN0cyIsIAogICAgICAgICAgICAgICAgICAgYXhpcy5sYWJlbHMgPSBjKCJUcmlhbCIsICJUYXNrIEV4cGVyaWVuY2UgKHllcykiLCJCb2FyZCBTaXplIChjbSkiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ3VwIERpc3RhbmNlIChjbSkiLCAiTm9ybWVkIEFnZSB4IERlbGF5XG4oc2hvcnQgdnMuIGxvbmcpIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTm9ybWVkIEFnZSB4IERlbGF5XG4oc2hvcnQgdnMuIG1lZGl1bSkiLCAiTm9ybWVkIEFnZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJEZWxheSAoc2hvcnQgdnMuIGxvbmcpIiwgIkRlbGF5IChzaG9ydCB2cy4gbWVkaXVtKSIpLCAKICAgICAgICAgICAgICAgICAgIG9yZGVyLnRlcm1zID0gYygyOjEsIDMsIDk6OCwgNSwgNiwgNCwgNyksIHdyYXAubGFiZWxzID0gRiwKICAgICAgICAgICAgICAgICAgIHdpZHRoID0gLjMsIHNob3cudmFsdWVzID0gVCwgdmFsdWUuc2l6ZSA9IDIuNSwgdmFsdWUub2Zmc2V0ID0gLjMpICsKICBmYWNldF93cmFwKH4gIiIpICsKICAjIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDEsIGx0eSA9IDIpICsKICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnMgPSAibG9nIiwgbGltaXRzID0gYyguMSwgNS41KSwgYnJlYWtzID0gYyguMSwgLjIsIC41LCAxLCAyLCA1KSkKCmZlLjIgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYyguNSwgNywgLjUsIC41KSwgImNtIikpCmBgYAoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZ2dzYXZlKCIuLi9ncmFwaHMvMDVfZm9yZXN0cGxvdC5wbmciLCBmZS4yLCB3aWR0aCA9IDMsIGhlaWdodCA9IDIuNSwgc2NhbGUgPSAyKQpgYGAKCiMjIFJhbmRvbSBlZmZlY3RzCgpgYGB7cn0KcmFuZWYucGxvdHMyIDwtIHBsb3RfbW9kZWwobW0uMiwgdHlwZSA9ICJyZSIsIHNvcnQuZXN0ID0gIihJbnRlcmNlcHQpIikKYGBgCgojIyMgU3ViamVjdC9TaXRlCgpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTh9CnJhbmVmLnBsb3RzMltbMV1dCmBgYAoKIyMjIFNpdGUKCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9M30KcmFuZWYucGxvdHMyW1syXV0KYGBgCgojIyMgU3BlY2llcwoKYGBge3IsIGZpZy53aWR0aD00LCBmaWcuaGVpZ2h0PTJ9CnJhbmVmLnBsb3RzMltbM11dCmBgYAoKIyBNb2RlbCAzCgotIGZ1cnRoZXIgcmVtb3ZlIHN1YmplY3Qvc2l0ZSByYW5kb20gZWZmZWN0cyB0byBsb29rIGF0IHNwZWNpZXMgZGlmZmVyZW5jZXMgKGZyb20gcHJlcmVnaXN0cmF0aW9uKQoKYGBge3J9Cm1tLjMgPC0gZ2xtZXIoY29ycmVjdCB+IGRlbGF5ICogbm9ybV9hZ2UgKwogICAgICAgICAgICAgICAgdGFza19leHBlcmllbmNlICsgY3VwX2Rpc3RhbmNlICsgdHJpYWwgKwogICAgICAgICAgICAgICAgKDEgKyBkZWxheSB8IHNwZWNpZXMpCiAgICAgICAgICAgICAgLCBkYXRhID0gbW9kZWwuZGF0YQogICAgICAgICAgICAgICwgZmFtaWx5ID0gYmlub21pYWwKICAgICAgICAgICAgICAsIGNvbnRyb2wgPSBnbG1lckNvbnRyb2wob3B0aW1pemVyID0gImJvYnlxYSIsIG9wdEN0cmwgPSBsaXN0KG1heGZ1biA9IDJlNSkpCiAgICAgICAgKQpgYGAKCiMjIFJhbmRvbSBlZmZlY3RzCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KcGh5bG8gPC0gcmVhZF9jc3YoIi4uL2RhdGEvc3BlY2llc19kYXRhLmNzdiIpICU+JSBzZWxlY3Qoc3BlY2llcywgc3BlY2llc19mb3JtYXR0ZWQsIHBoeWxvKQpgYGAKCmBgYHtyfQpwbG90LmRhdGEgPC0gZ2V0X21vZGVsX2RhdGEobW0uMywgdHlwZSA9ICJyZSIsIHNob3cudmFsdWVzID0gVCkgJT4lCiAgbGVmdF9qb2luKHBoeWxvLCBieSA9IGMoInRlcm0iID0gInNwZWNpZXMiKSkgJT4lCiAgcmVuYW1lKHNwZWNpZXMgPSBzcGVjaWVzX2Zvcm1hdHRlZCkgJT4lCiAgbXV0YXRlKAogICAgZmFjZXQgPSBmY3RfcmV2KGZhY2V0KSwKICAgIGZhY2V0ID0gZmN0X3JlY29kZShmYWNldCwgIkludGVyY2VwdCIgPSAic3BlY2llcyAoSW50ZXJjZXB0KSIsCiAgICAgICAgICAgICAgICAgICAgICAgIkRlbGF5IChzaG9ydCB2cy4gbG9uZykiID0gImRlbGF5bG9uZyIsIAogICAgICAgICAgICAgICAgICAgICAgICJEZWxheSAoc2hvcnQgdnMuIG1lZGl1bSkiID0gImRlbGF5bWVkaXVtIikKICAgICkKCnNvcnRlZCA8LSBmaWx0ZXIocGxvdC5kYXRhLCBmYWNldCA9PSAiSW50ZXJjZXB0IikgJT4lIGFycmFuZ2UoZXN0aW1hdGUpICU+JSB3aXRoKHNwZWNpZXMpCnBsb3QuZGF0YSA8LSBtdXRhdGUocGxvdC5kYXRhLCBzcGVjaWVzID0gZmFjdG9yKHNwZWNpZXMsIGxldmVscyA9IHNvcnRlZCkpCmBgYAoKYGBge3IsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTIuNX0KcmUuMyA8LSBnZ3Bsb3QocGxvdC5kYXRhLCBhZXMoeCA9IHNwZWNpZXMsIHkgPSBlc3RpbWF0ZSwgY29sID0gZ3JvdXApKSArCiAgZmFjZXRfZ3JpZCh+IGZhY2V0KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMSwgY29sID0gImdyZXk5MCIsIHNpemUgPSAxLjEyNSkgKwogICMgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMSwgbHR5ID0gMikgKwogIGdlb21fZXJyb3JiYXIoYWVzKHltaW4gPSBjb25mLmxvdywgeW1heCA9IGNvbmYuaGlnaCksIHdpZHRoID0gLjMpICsKICBnZW9tX3BvaW50KHNpemUgPSAyLjUpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcC5sYWJlbCksIG51ZGdlX3ggPSAuMywgc2l6ZSA9IDIuNSwgc2hvdy5sZWdlbmQgPSBGKSArCiAgc2NhbGVfeV9jb250aW51b3VzKCJPZGRzIFJhdGlvcyIsIHRyYW5zID0gImxvZyIsIGJyZWFrcyA9IGMoLjEsIC4yLCAuNSwgMSwgMiwgNSwxMCwyMCw0MCkpICsKICBzY2FsZV9jb2xvdXJfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsKICBjb29yZF9mbGlwKHlsaW0gPSBjKC4xLCA0Mi41KSkgKyB4bGFiKCIiKSArCiAgZ3VpZGVzKGNvbCA9ICJub25lIikgKwogIGdndGl0bGUoIkIuIFNwZWNpZXMgUmFuZG9tIEVmZmVjdHMiKQpgYGAKCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD0zfQptYXQgPC0gbWF0cml4KGMocmVwKDEsIDMpLCByZXAoMiwgNykpLCBucm93ID0gMSkKZ3JpZC5hcnJhbmdlKGZlLjIsIHJlLjMsIGxheW91dF9tYXRyaXggPSBtYXQpCmBgYAoKYGBge3IsIHdhcm5pbmc9RkFMU0V9Cmdnc2F2ZSgiLi4vZ3JhcGhzLzA1X2ZvcmVzdHBsb3RfZmVfcmUucG5nIiwgYXJyYW5nZUdyb2IoZmUuMiwgcmUuMywgbGF5b3V0X21hdHJpeCA9IG1hdCksIHdpZHRoID0gOCwgaGVpZ2h0ID0gMywgc2NhbGUgPSAxLjgpCgpnZ3NhdmUoIi4uL2dyYXBocy9GaWczLnRpZmYiLCBhcnJhbmdlR3JvYihmZS4yLCByZS4zLCBsYXlvdXRfbWF0cml4ID0gbWF0KSwgd2lkdGggPSA4LCBoZWlnaHQgPSAzLCBzY2FsZSA9IDEuOCwgdHlwZSA9ICJjYWlybyIsIGNvbXByZXNzaW9uID0gImx6dyIpCmBgYAoKCiMgTW9kZWwgY29tcGFyaXNvbgoKV2UncmUgbG9va2luZyBmb3IgdGhlIGxvd2VzdCBBSUMoYykgYXMgdGhlIG1vZGVsIHdpdGggdGhlICdiZXN0IGZpdCcgd2l0aCBhIHJlYXNvbmFibGUgbnVtYmVyIG9mIHBhcmFtZXRlcnMuIChUb28gbWFueSBhcmUgcGVuYWxpemVkIGJ5IEFJQyBhcyBvbmUgd2F5IHRvIGFkZHJlc3Mgb3ZlcmZpdHRpbmcuKQoKSW5kZWVkLCB0aGUgcmVkdWNlZCBtb2RlbCBzZWVtcyB0byBkbyBhIGJldHRlciBqb2Igb2Ygc3RyaWtpbmcgdGhhdCBiYWxhbmNlIGJldHdlZW4gZml0dGluZyB0aGUgZGF0YSB3aXRoIGZld2VyIHBhcmFtZXRlcnMuCgpgYGB7cn0KQUlDY3RhYihtbS4xLCBtbS4yLCBtbS4zLCBsb2dMaWsgPSBULCB3ZWlnaHRzID0gVCkKYGBgCgpgYGB7cn0KYW5vdmEobW0uMSwgbW0uMiwgbW0uMykKYGBgCgojIyBEaWZmZXJlbmNlIGluIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzCgpEaWZmZXJlbmNlCgpgYGB7cn0KY29lZjEgPC0gY29lZihzdW1tYXJ5KG1tLjEpKVtjKDIsIDMsIDYpLCAxXQpjb2VmMiA8LSBjb2VmKHN1bW1hcnkobW0uMikpW2MoMiwgMywgNiksIDFdCgpjb2VmMiAtIGNvZWYxCmBgYAoKRGlmZmVyZW5jZSBpbiBvZGRzIHJhdGlvcwoKYGBge3J9CmV4cChjb2VmMikgLSBleHAoY29lZjEpCmBgYAoK